From ae7cb2ccc84cf25332d1efabeb197a5e1b05d029 Mon Sep 17 00:00:00 2001
From: Ankit Bahl <ankit2bahl@gmail.com>
Date: Fri, 19 Jan 2024 13:38:18 -0800
Subject: Added search bar for expense list page (#52)

* Added search bar for expense list page

* Change search input styling

---------

Co-authored-by: Sebastien Castiel <sebastien@castiel.me>
---
 src/app/groups/[groupId]/expenses/expense-list.tsx | 164 ++++++++++++---------
 1 file changed, 91 insertions(+), 73 deletions(-)

(limited to 'src/app/groups/[groupId]/expenses/expense-list.tsx')

diff --git a/src/app/groups/[groupId]/expenses/expense-list.tsx b/src/app/groups/[groupId]/expenses/expense-list.tsx
index 73c8c6e..b083ab6 100644
--- a/src/app/groups/[groupId]/expenses/expense-list.tsx
+++ b/src/app/groups/[groupId]/expenses/expense-list.tsx
@@ -1,6 +1,7 @@
 'use client'
 import { CategoryIcon } from '@/app/groups/[groupId]/expenses/category-icon'
 import { Button } from '@/components/ui/button'
+import { SearchBar } from '@/components/ui/search-bar'
 import { getGroupExpenses } from '@/lib/api'
 import { cn } from '@/lib/utils'
 import { Expense, Participant } from '@prisma/client'
@@ -8,7 +9,7 @@ import dayjs, { type Dayjs } from 'dayjs'
 import { ChevronRight } from 'lucide-react'
 import Link from 'next/link'
 import { useRouter } from 'next/navigation'
-import { Fragment, useEffect } from 'react'
+import { Fragment, useEffect, useState } from 'react'
 
 type Props = {
   expenses: Awaited<ReturnType<typeof getGroupExpenses>>
@@ -63,6 +64,7 @@ export function ExpenseList({
   participants,
   groupId,
 }: Props) {
+  const [searchText, setSearchText] = useState('')
   useEffect(() => {
     const activeUser = localStorage.getItem('newGroup-activeUser')
     const newUser = localStorage.getItem(`${groupId}-newUser`)
@@ -86,86 +88,102 @@ export function ExpenseList({
   const router = useRouter()
 
   const groupedExpensesByDate = getGroupedExpensesByDate(expenses)
-
   return expenses.length > 0 ? (
-    Object.values(EXPENSE_GROUPS).map((expenseGroup: string) => {
-      const groupExpenses = groupedExpensesByDate[expenseGroup]
-      if (!groupExpenses) return null
-      return (
-        <div key={expenseGroup}>
-          <div
-            className={
-              'text-muted-foreground text-xs pl-4 sm:pl-6 py-1 font-semibold sticky top-16 bg-white dark:bg-[#1b1917]'
-            }
-          >
-            {expenseGroup}
-          </div>
-          {groupExpenses.map((expense: any) => (
+    <>
+      <SearchBar onChange={(e) => setSearchText(e.target.value)} />
+      {Object.values(EXPENSE_GROUPS).map((expenseGroup: string) => {
+        const groupExpenses = groupedExpensesByDate[expenseGroup]
+        if (!groupExpenses) return null
+        return (
+          <div key={expenseGroup}>
             <div
-              key={expense.id}
-              className={cn(
-                'flex justify-between sm:mx-6 px-4 sm:rounded-lg sm:pr-2 sm:pl-4 py-4 text-sm cursor-pointer hover:bg-accent gap-1 items-stretch',
-                expense.isReimbursement && 'italic',
-              )}
-              onClick={() => {
-                router.push(`/groups/${groupId}/expenses/${expense.id}/edit`)
-              }}
+              className={
+                'text-muted-foreground text-xs pl-4 sm:pl-6 py-1 font-semibold sticky top-16 bg-white dark:bg-[#1b1917]'
+              }
             >
-              <CategoryIcon
-                category={expense.category}
-                className="w-4 h-4 mr-2 mt-0.5 text-muted-foreground"
-              />
-              <div className="flex-1">
-                <div
-                  className={cn('mb-1', expense.isReimbursement && 'italic')}
-                >
-                  {expense.title}
-                </div>
-                <div className="text-xs text-muted-foreground">
-                  Paid by{' '}
-                  <strong>{getParticipant(expense.paidById)?.name}</strong> for{' '}
-                  {expense.paidFor.map((paidFor: any, index: number) => (
-                    <Fragment key={index}>
-                      {index !== 0 && <>, </>}
-                      <strong>
-                        {
-                          participants.find(
-                            (p) => p.id === paidFor.participantId,
-                          )?.name
-                        }
-                      </strong>
-                    </Fragment>
-                  ))}
-                </div>
-              </div>
-              <div className="flex flex-col justify-between items-end">
+              {expenseGroup}
+            </div>
+            {groupExpenses
+              .filter(
+                (exp) =>
+                  exp.title.toLowerCase().match(searchText.toLowerCase()) !==
+                  null,
+              )
+              .map((expense: any) => (
                 <div
+                  key={expense.id}
                   className={cn(
-                    'tabular-nums whitespace-nowrap',
-                    expense.isReimbursement ? 'italic' : 'font-bold',
+                    'flex justify-between sm:mx-6 px-4 sm:rounded-lg sm:pr-2 sm:pl-4 py-4 text-sm cursor-pointer hover:bg-accent gap-1 items-stretch',
+                    expense.isReimbursement && 'italic',
                   )}
+                  onClick={() => {
+                    router.push(
+                      `/groups/${groupId}/expenses/${expense.id}/edit`,
+                    )
+                  }}
                 >
-                  {currency} {(expense.amount / 100).toFixed(2)}
-                </div>
-                <div className="text-xs text-muted-foreground">
-                  {formatDate(expense.expenseDate)}
+                  <CategoryIcon
+                    category={expense.category}
+                    className="w-4 h-4 mr-2 mt-0.5 text-muted-foreground"
+                  />
+                  <div className="flex-1">
+                    <div
+                      className={cn(
+                        'mb-1',
+                        expense.isReimbursement && 'italic',
+                      )}
+                    >
+                      {expense.title}
+                    </div>
+                    <div className="text-xs text-muted-foreground">
+                      Paid by{' '}
+                      <strong>{getParticipant(expense.paidById)?.name}</strong>{' '}
+                      for{' '}
+                      {expense.paidFor.map((paidFor: any, index: number) => (
+                        <Fragment key={index}>
+                          {index !== 0 && <>, </>}
+                          <strong>
+                            {
+                              participants.find(
+                                (p) => p.id === paidFor.participantId,
+                              )?.name
+                            }
+                          </strong>
+                        </Fragment>
+                      ))}
+                    </div>
+                  </div>
+                  <div className="flex flex-col justify-between items-end">
+                    <div
+                      className={cn(
+                        'tabular-nums whitespace-nowrap',
+                        expense.isReimbursement ? 'italic' : 'font-bold',
+                      )}
+                    >
+                      {currency} {(expense.amount / 100).toFixed(2)}
+                    </div>
+                    <div className="text-xs text-muted-foreground">
+                      {formatDate(expense.expenseDate)}
+                    </div>
+                  </div>
+                  <Button
+                    size="icon"
+                    variant="link"
+                    className="self-center hidden sm:flex"
+                    asChild
+                  >
+                    <Link
+                      href={`/groups/${groupId}/expenses/${expense.id}/edit`}
+                    >
+                      <ChevronRight className="w-4 h-4" />
+                    </Link>
+                  </Button>
                 </div>
-              </div>
-              <Button
-                size="icon"
-                variant="link"
-                className="self-center hidden sm:flex"
-                asChild
-              >
-                <Link href={`/groups/${groupId}/expenses/${expense.id}/edit`}>
-                  <ChevronRight className="w-4 h-4" />
-                </Link>
-              </Button>
-            </div>
-          ))}
-        </div>
-      )
-    })
+              ))}
+          </div>
+        )
+      })}
+    </>
   ) : (
     <p className="px-6 text-sm py-6">
       Your group doesn’t contain any expense yet.{' '}
-- 
cgit v1.3

